﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Raven.Client;
using Topics.Radical.Validation;
using Topics.Radical.Linq;
using Raven.Abstractions.Exceptions;
using SampleApp.MvcBackend.ComponentModel;

namespace SampleApp.MvcBackend.Services
{
	class RepositoryFactory : IRepositoryFactory
	{
		class Repository : IRepository
		{
			public void Dispose()
			{
				this.session.Dispose();

				this.aggregateWithPendingCommits.Clear();
			}

			readonly ICommitDispatchScheduler commitDispatchScheduler;
			readonly IDocumentSession session;
			readonly HashSet<IAggregate> aggregateWithPendingCommits = new HashSet<IAggregate>();
			readonly Guid txId;

			public Repository( IDocumentSession session, ICommitDispatchScheduler commitDispatchScheduler )
			{
				Ensure.That( session ).Named( () => session ).IsNotNull();
				Ensure.That( commitDispatchScheduler ).Named( () => commitDispatchScheduler ).IsNotNull();

				this.txId = Guid.NewGuid();

				this.session = session;
				this.session.Advanced.UseOptimisticConcurrency = true;

				this.commitDispatchScheduler = commitDispatchScheduler;
			}

			public void Save<TAggregate>( TAggregate aggregate ) where TAggregate : IAggregate
			{
				if ( this.aggregateWithPendingCommits.Contains( aggregate ) )
				{
					throw new InvalidOperationException( "Cannot save the same aggregate more than once in the same session." );
				}

				this.session.Store( aggregate );

				this.aggregateWithPendingCommits.Add( aggregate );
			}

			public void CommitChanges()
			{
				try
				{
					var commits = this.aggregateWithPendingCommits.Select( a => Commit.CreateFor( this.txId, a ) )
						.ToArray()
						.ForEach( commit => this.session.Store( commit ) );

					this.session.SaveChanges();

					this.aggregateWithPendingCommits.ForEach( a => a.ClearUncommittedEvents() );
					this.aggregateWithPendingCommits.Clear();

					this.commitDispatchScheduler.ScheduleDispatch( commits );
				}
				catch ( ConcurrencyException cex )
				{
					//TODO: log
					throw;
				}
			}

			public TAggregate GetById<TAggregate>( string aggregateId ) where TAggregate : IAggregate
			{
				return this.session.Load<TAggregate>( aggregateId );
			}

			public TAggregate[] GetById<TAggregate>( params string[] aggregateIds ) where TAggregate : IAggregate
			{
				return this.session.Load<TAggregate>( aggregateIds );
			}
		}

		readonly ICommitDispatchScheduler commitDispatcher;
		readonly IDocumentStore documentSessionFactory;

		public RepositoryFactory( IDocumentStore documentSessionFactory, ICommitDispatchScheduler commitDispatcher )
		{
			Ensure.That( documentSessionFactory ).Named( () => documentSessionFactory ).IsNotNull();
			Ensure.That( commitDispatcher ).Named( () => commitDispatcher ).IsNotNull();

			this.documentSessionFactory = documentSessionFactory;
			this.commitDispatcher = commitDispatcher;
		}

		public IRepository OpenSession()
		{
			return new Repository( this.documentSessionFactory.OpenSession(), this.commitDispatcher );
		}
	}
}
